package com.ethan.blog.interceptor;

import com.ethan.blog.common.constant.MessageConstant;
import com.ethan.blog.common.constant.ResponseStatusCodeConstant;
import com.ethan.blog.common.exception.IdentityExpiredException;
import com.ethan.blog.common.utils.JwtUtil;
import com.ethan.blog.common.utils.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.Map;

/**
 * 登录拦截器
 *
 * @author Ethan
 * @date 2024-5-27
 */
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 这个类中的函数是Spring Boot拦截器`LoginInterceptor`的两个重要方法：`preHandle`和`afterCompletion`。
     * <p>
     * 1. `preHandle`方法：这个方法在请求被实际处理之前被调用。在这个方法中，首先从请求头中获取名为"Authorization"的令牌，然后使用`JwtUtil.parseToken`方法解析这个令牌，并将解析出的用户信息存入ThreadLocal。如果令牌解析成功，那么方法返回`true`，请求可以继续。如果令牌解析失败，那么方法返回`false`，并且设置响应的状态码为401，表示未授权。
     * <p>
     * 2. `afterCompletion`方法：这个方法在请求处理完毕后，也就是在Controller的方法执行后并且视图渲染完成后，被调用。在这个方法中，清除了ThreadLocal中的用户信息，防止内存泄漏。
     * <p>
     * 总的来说，`preHandle`方法在请求处理之前执行，用于验证请求的令牌并获取用户信息；`afterCompletion`方法在请求处理完毕后执行，用于清理资源。
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        boolean result;
        // todo: 如果非api请求，例如静态资源请求（如HTML、CSS、JavaScript文件等）存在重要私密信息，则修改该条信息
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        // todo：swagger更换拦截方式
        // 判断如果请求的类是swagger的控制器，直接放行
        // 判断如果请求是 Swagger 相关的,直接放行
        HttpServletRequest httpServletRequest = request;
        String requestUrl = httpServletRequest.getRequestURI();
        log.info("请求地址：requestUrl = {}", requestUrl);
        if (requestUrl.startsWith("/ethan-blog/v3/api-docs") || requestUrl.startsWith("/ethan-blog/swagger-ui") || requestUrl.startsWith("/ethan-blog/error")) {
            log.info("访问swagger，直接放行");
            return true;
        }

        // 令牌验证 todo: 设置 "Authorization" 取值方式
        String token = request.getHeader("Authorization");
        log.info("携带 Authorization = " + token);
        try {
            // 从redis中获取token,判断是否过期
            String redisToken = stringRedisTemplate.opsForValue().get(token);
            if (redisToken == null) {
                throw new IdentityExpiredException(MessageConstant.IDENTITY_EXPIRED);
            }
            // 解析token
            Map<String, Object> claims = JwtUtil.parseToken(token);
            claims.put("token", token);
            log.info("令牌验证成功，用户信息：{}", claims);
            // 将用户信息存入ThreadLocal
            ThreadLocalUtil.set(claims);
            result = true;
        } catch (Exception e) {
            log.error("令牌验证失败: {}", e.getMessage());
            response.setStatus(ResponseStatusCodeConstant.NOT_LOGIN_CODE);
            result = false;
        }
        return result;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清除ThreadLocal
        ThreadLocalUtil.remove();
        log.info("清除ThreadLocal成功，claims = {}", (String) ThreadLocalUtil.get());
    }
}
